Skip to content

ClickHouse: Add fuzz target comparing the tokenizer with ClickHouse's lexer - #2646

Open
s5dsn-eqee wants to merge 1 commit into
apache:mainfrom
s5dsn-eqee:fuzz-clickhouse-lexer
Open

s5dsn-eqee wants to merge 1 commit into
apache:mainfrom
s5dsn-eqee:fuzz-clickhouse-lexer

Conversation

@s5dsn-eqee

Copy link
Copy Markdown
Contributor

Closes: #2645

Dialects: ClickHouse

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.20%. Comparing base (a80289f) to head (7fa5a53).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #2646   +/-   ##
=======================================
  Coverage   81.20%   81.20%           
=======================================
  Files          42       42           
  Lines       33893    33893           
  Branches    33893    33893           
=======================================
  Hits        27523    27523           
  Misses       2796     2796           
  Partials     3574     3574           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@LucaCappelletti94

Copy link
Copy Markdown
Contributor

FYI, the ClusterFuzzLite error is due to duckDB shenanigans. Hopefully will be patched soon. Will review the PR shortly.

@LucaCappelletti94 LucaCappelletti94 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest the objectively non trivial option to go for a crate for the ClickHouse lexer. fuzz/build.rs fails when c++ is GCC with reference to 'std' is ambiguous at Lexer.cpp:526, which breaks --all-features, and the C ABI returns token kinds as bare numbers. The other oracle targets each depend on a crate (libsqlite3-sys, pg_query, libduckdb-sys), and https://github.com/LucaCappelletti94/sqlcipher-src is an example of a crate that tracks an upstream C codebase.

Such a crate should ship clickhouse-lexer's upstream/ files unchanged, compile them with clang++, and expose a safe Lexer yielding tokens with a TokenKind enum, along with the ClickHouse commit the files come from. Every update should run clickhouse-lexer's own test suite (test/test.js) on the shipped sources before release, next to the crate's tests. A scheduled workflow, like clickhouse-lexer's sync.yml for its own releases, should open the update PR by itself whenever the lexer changes, so nobody has to remember to bump it.

The best home is ClickHouse/clickhouse-lexer itself, and you could offer the crate there on ClickHouse/clickhouse-lexer#2. That issue has had no answer since September, so publishing from your own repository as clickhouse-lexer-sys and offering to transfer it later avoids blocking on them. This PR then keeps only the target and the docs, and the lone $ check below becomes kind == TokenKind::DollarSign.

I understand it is quite a bit of a challenge, and it is a reasonable ask only in this brave new world of LLMs given every step I described is rather mechanic and fully testable & verifiable. Would you be up for it?

Comment thread fuzz/build.rs
Comment on lines +23 to +24
cc::Build::new()
.cpp(true)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should pin the compiler to clang++. When c++ is GCC (the default on Ubuntu), cargo +nightly fuzz build --features clickhouse fails with reference to 'std' is ambiguous at Lexer.cpp:526.

Suggested change
cc::Build::new()
.cpp(true)
cc::Build::new()
// LexerStandalone.h declares a `std` namespace that GCC's predeclared one makes ambiguous.
.compiler("clang++")
.cpp(true)

But this is only relevant if we go for git submodules instead of a crate.

Comment on lines +63 to +65
if clickhouse_lexer_token_is_end(token) != 0 {
return true;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should count a lone $ as rejected. ClickHouse lexes it as DollarSign, which only its JSONPath parser consumes, so no statement holding one parses.

Furthermore, ClickHouse 26.9 rejects SELECT $$, SELECT [$], SELECT 1 AS $ and SELECT * FROM t WHERE x = $, yet the target reports SELECT $$ as a tokenizer bug.

I tested an 8-minute -fork=6 run, and 9190 of the 46504 crashes were this case. A one-byte $ token is always DollarSign, and that held for all 46504 crashes.

Suggested change
if clickhouse_lexer_token_is_end(token) != 0 {
return true;
}
if clickhouse_lexer_token_is_end(token) != 0 {
return true;
}
// A lone `$` lexes as DollarSign, which no ClickHouse SQL statement accepts.
if token_end.offset_from(token_begin) == 1 && *token_begin == b'$' as c_char {
return false;
}

fn clickhouse_lexer_token_is_end(token: c_uchar) -> c_int;
}

/// Whether ClickHouse's lexer reads `sql` to the end without an error token.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the lone $ check, the doc comment should name it as it is hardly obvious.

Suggested change
/// Whether ClickHouse's lexer reads `sql` to the end without an error token.
/// Whether ClickHouse's lexer reads `sql` to the end without an error token or a lone `$`.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ClickHouse: Add fuzz target comparing the tokenizer with ClickHouse's lexer

3 participants